Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / telephone_manager.py

Displaying Raw • Download


meshchatx/src/backend/telephone_manager.py 33db12b971deeddf7b63d3e96fb5123a355b7986 (33db12b9) Text, 18.56 KB

Tff7b72import T7ee787asyncio
Tff7b72import T7ee787base64
Tff7b72import T7ee787contextlib
Tff7b72import T7ee787os
Tff7b72import T7ee787time

Tff7b72import T7ee787RNS
Tff7b72from T7ee787LXST Tff7b72import Te6edf3Telephone

Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787meshchat_utils Tff7b72import Tb4b4b4(
Te6edf3hex_identifier_to_bytesTb4b4b4,
Te6edf3normalize_hex_identifierTb4b4b4,
Tb4b4b4)


Tff7b72class T56d364TeeTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3sinkTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffsinks Tff7b72= Tb4b4b4[Te6edf3sinkTb4b4b4]

Tff7b72def Td2a8ffadd_sinkTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3sinkTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3sink Tff7b72not Tff7b72in Tff7b72selfTff7b72.Td2a8ffsinksTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffsinksTff7b72.Td2a8ffappendTb4b4b4(Te6edf3sinkTb4b4b4)

Tff7b72def Td2a8ffremove_sinkTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3sinkTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3sink Tff7b72in Tff7b72selfTff7b72.Td2a8ffsinksTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffsinksTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3sinkTb4b4b4)

Tff7b72def Td2a8ffhandle_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3frameTb4b4b4, Te6edf3sourceTb4b4b4)Tb4b4b4:
Tff7b72for Te6edf3sink Tff7b72in Tff7b72selfTff7b72.Td2a8ffsinksTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3sinkTff7b72.Td2a8ffhandle_frameTb4b4b4(Te6edf3frameTb4b4b4, Te6edf3sourceTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffTee: Error in sink handle_frame: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)

Tff7b72def Td2a8ffcan_receiveTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3from_sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tffa657anyTb4b4b4(Te6edf3sinkTff7b72.Td2a8ffcan_receiveTb4b4b4(Te6edf3from_sourceTb4b4b4) Tff7b72for Te6edf3sink Tff7b72in Tff7b72selfTff7b72.Td2a8ffsinksTb4b4b4)


Tff7b72class T56d364TelephoneManagerTb4b4b4:
T8b949e# LXST Status Constants for reference:
T8b949e# 0: STATUS_BUSY
T8b949e# 1: STATUS_REJECTED
T8b949e# 2: STATUS_CALLING
T8b949e# 3: STATUS_AVAILABLE
T8b949e# 4: STATUS_RINGING
T8b949e# 5: STATUS_CONNECTING
T8b949e# 6: STATUS_ESTABLISHED

Tff7b72def Tff7b72__init__Tb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3identityTb4b4b4: Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4,
Te6edf3config_managerTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3storage_dirTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3dbTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72= Te6edf3identity
Tff7b72selfTff7b72.Td2a8ffconfig_manager Tff7b72= Te6edf3config_manager
Tff7b72selfTff7b72.Td2a8ffstorage_dir Tff7b72= Te6edf3storage_dir
Tff7b72selfTff7b72.Td2a8ffdb Tff7b72= Te6edf3db
Tff7b72selfTff7b72.Td2a8ffget_name_for_identity_hash Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrecordings_dir Tff7b72= Tb4b4b4(
Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_dirTb4b4b4, Ta5d6ff"Ta5d6ffrecordingsTa5d6ff"Tb4b4b4) Tff7b72if Te6edf3storage_dir Tff7b72else Tff7b72None
Tb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrecordings_dirTb4b4b4:
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Tff7b72selfTff7b72.Td2a8ffrecordings_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)

Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffon_ringing_callback Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffon_established_callback Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffon_ended_callback Tff7b72= Tff7b72None

Tff7b72selfTff7b72.Td2a8ffcall_start_time Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffcall_status_at_end Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffcall_is_incoming Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ffcall_was_established Tff7b72= Tff7b72False

T8b949e# Manual mute overrides in case LXST internal muting is buggy
Tff7b72selfTff7b72.Td2a8fftransmit_muted Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ffreceive_muted Tff7b72= Tff7b72False

Tff7b72selfTff7b72.Td2a8ffinitiation_status Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffinitiation_target_hash Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffon_initiation_status_callback Tff7b72= Tff7b72None

Tf0883e@property
Tff7b72def Td2a8ffis_recordingTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False

Tff7b72def Td2a8ffinit_telephoneTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72return

Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72= Te6edf3TelephoneTb4b4b4(Tff7b72selfTff7b72.Td2a8ffidentityTb4b4b4)
T8b949e# Disable busy tone played on caller side when remote side rejects, or doesn't answer
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffset_busy_tone_timeTb4b4b4(T79c0ff0Tb4b4b4)
T8b949e# Increase connection timeout for slower networks
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffset_connect_timeoutTb4b4b4(T79c0ff30Tb4b4b4)

T8b949e# Set initial profile from config
Tff7b72if Tff7b72selfTff7b72.Td2a8ffconfig_managerTb4b4b4:
Te6edf3profile_id Tff7b72= Tff7b72selfTff7b72.Td2a8ffconfig_managerTff7b72.Td2a8fftelephone_audio_profile_idTff7b72.Td2a8ffgetTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffswitch_profileTb4b4b4(Te6edf3profile_idTb4b4b4)

Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffset_ringing_callbackTb4b4b4(Tff7b72selfTff7b72.Td2a8ffon_telephone_ringingTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffset_established_callbackTb4b4b4(Tff7b72selfTff7b72.Td2a8ffon_telephone_call_establishedTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffset_ended_callbackTb4b4b4(Tff7b72selfTff7b72.Td2a8ffon_telephone_call_endedTb4b4b4)

Tff7b72def Td2a8ffteardownTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72= Tff7b72None

Tff7b72def Td2a8ffhangupTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffhangupTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffTelephoneManager: Error during hangup: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)

T8b949e# Always clear initiation status on hangup to prevent "Dialing..." hang
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Tff7b72NoneTb4b4b4, Tff7b72NoneTb4b4b4)

Tff7b72def Td2a8ffregister_ringing_callbackTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3callbackTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_ringing_callback Tff7b72= Te6edf3callback

Tff7b72def Td2a8ffregister_established_callbackTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3callbackTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_established_callback Tff7b72= Te6edf3callback

Tff7b72def Td2a8ffregister_ended_callbackTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3callbackTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_ended_callback Tff7b72= Te6edf3callback

Tff7b72def Td2a8ffset_callbacksTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3ringingTff7b72=Tff7b72NoneTb4b4b4, Te6edf3establishedTff7b72=Tff7b72NoneTb4b4b4, Te6edf3endedTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3ringingTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffregister_ringing_callbackTb4b4b4(Te6edf3ringingTb4b4b4)
Tff7b72if Te6edf3establishedTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffregister_established_callbackTb4b4b4(Te6edf3establishedTb4b4b4)
Tff7b72if Te6edf3endedTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffregister_ended_callbackTb4b4b4(Te6edf3endedTb4b4b4)

Tff7b72def Td2a8ffon_telephone_ringingTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3caller_identityTb4b4b4: Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffRinging...Ta5d6ff"Tb4b4b4)
Tff7b72return

Tff7b72selfTff7b72.Td2a8ffcall_start_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcall_is_incoming Tff7b72= Tff7b72True
Tff7b72selfTff7b72.Td2a8ffcall_was_established Tff7b72= Tff7b72False
Tff7b72if Tff7b72selfTff7b72.Td2a8ffon_ringing_callbackTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_ringing_callbackTb4b4b4(Te6edf3caller_identityTb4b4b4)

Tff7b72def Td2a8ffon_telephone_call_establishedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3caller_identityTb4b4b4: Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4)Tb4b4b4:
T8b949e# Update start time to when it was actually established for duration calculation
Tff7b72selfTff7b72.Td2a8ffcall_start_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcall_was_established Tff7b72= Tff7b72True

T8b949e# Track per-call stats from the active link (uses RNS Link counters)
Te6edf3link Tff7b72= Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4, Ta5d6ff"Ta5d6ffactive_callTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcall_stats Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6fflinkTa5d6ff"Tb4b4b4: Te6edf3linkTb4b4b4,
Tb4b4b4}

Tff7b72if Tff7b72selfTff7b72.Td2a8ffon_established_callbackTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_established_callbackTb4b4b4(Te6edf3caller_identityTb4b4b4)

Tff7b72def Td2a8ffon_telephone_call_endedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3caller_identityTb4b4b4: Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4)Tb4b4b4:
T8b949e# Capture status just before ending if possible, or use the last known status
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffcall_status_at_end Tff7b72= Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status

T8b949e# Ensure initiation status is cleared when call ends
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Tff7b72NoneTb4b4b4, Tff7b72NoneTb4b4b4)

Tff7b72if Tff7b72selfTff7b72.Td2a8ffon_ended_callbackTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_ended_callbackTb4b4b4(Te6edf3caller_identityTb4b4b4)

Tff7b72def Td2a8ffstart_recordingTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# Disabled for now as LXST does not have a Tee to use
Tff7b72pass

Tff7b72def Td2a8ffstop_recordingTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# Disabled for now
Tff7b72pass

Tff7b72def Td2a8ffannounceTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3attached_interfaceTff7b72=Tff7b72NoneTb4b4b4, Te6edf3display_nameTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
Tff7b72if Te6edf3display_nameTb4b4b4:
Tff7b72import T7ee787RNST7ee787.T7ee787vendorT7ee787.T7ee787umsgpack Tff7b72as T7ee787msgpack

T8b949e# Pack display name in LXMF-compatible app data format
Te6edf3app_data Tff7b72= Te6edf3msgpackTff7b72.Td2a8ffpackbTb4b4b4(Tb4b4b4[Te6edf3display_nameTb4b4b4, Tff7b72NoneTb4b4b4, Tff7b72NoneTb4b4b4]Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffannounceTb4b4b4(
Te6edf3app_dataTff7b72=Te6edf3app_dataTb4b4b4,
Te6edf3attached_interfaceTff7b72=Te6edf3attached_interfaceTb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8fflast_announce Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffannounceTb4b4b4(Te6edf3attached_interfaceTff7b72=Te6edf3attached_interfaceTb4b4b4)

Tff7b72def Td2a8ff_update_initiation_statusTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3statusTb4b4b4, Te6edf3target_hashTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffinitiation_status Tff7b72= Te6edf3status
Tff7b72if Te6edf3target_hash Tff7b72is Tff7b72not Tff7b72None Tff7b72or Te6edf3status Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffinitiation_target_hash Tff7b72= Te6edf3target_hash
Tff7b72if Tff7b72selfTff7b72.Td2a8ffon_initiation_status_callbackTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffon_initiation_status_callbackTb4b4b4(
Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4,
Tff7b72selfTff7b72.Td2a8ffinitiation_target_hashTb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffTelephoneManager: Error in initiation status callback: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4,
Tb4b4b4)

Tff7b72async Tff7b72def Td2a8ffinitiateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3timeout_secondsTb4b4b4: Tffa657int Tff7b72= T79c0ff15Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffTelephone is not initializedTa5d6ff"
Tff7b72raise Tf85149RuntimeErrorTb4b4b4(Te6edf3msgTb4b4b4)

Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffbusy Tff7b72or Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffTelephone is already in useTa5d6ff"
Tff7b72raise Tf85149RuntimeErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3destination_hash_hex Tff7b72= Te6edf3destination_hashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffResolving identity...Ta5d6ff"Tb4b4b4, Te6edf3destination_hash_hexTb4b4b4)

Tff7b72tryTb4b4b4:

Tff7b72def Td2a8ffresolve_identityTb4b4b4(Te6edf3target_hash_hexTb4b4b4)Tb4b4b4:
T8b949e"""Resolve identity from multiple hints: direct recall, destination_hash announce, identity_hash announce, or public key."""
Te6edf3target_hash Tff7b72= Te6edf3hex_identifier_to_bytesTb4b4b4(Te6edf3target_hash_hexTb4b4b4)
Tff7b72if Tff7b72not Te6edf3target_hashTb4b4b4:
Tff7b72return Tff7b72None

T8b949e# 1) Direct recall (identity hash)
Te6edf3ident Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8ffrecallTb4b4b4(Te6edf3target_hashTb4b4b4)
Tff7b72if Te6edf3identTb4b4b4:
Tff7b72return Te6edf3ident

Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdbTb4b4b4:
Tff7b72return Tff7b72None

Te6edf3th Tff7b72= Te6edf3target_hash_hexTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3canonical Tff7b72= Te6edf3normalize_hex_identifierTb4b4b4(Te6edf3thTb4b4b4)

T8b949e# 2) By destination_hash (could be lxst.telephony or lxmf.delivery hash)
Te6edf3announce Tff7b72= Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_announce_by_hashTb4b4b4(Te6edf3thTb4b4b4)
Tff7b72if Tff7b72not Te6edf3announce Tff7b72and Te6edf3canonicalTb4b4b4:
Te6edf3announce Tff7b72= Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_announce_by_hashTb4b4b4(Te6edf3canonicalTb4b4b4)
Tff7b72if Tff7b72not Te6edf3announceTb4b4b4:
T8b949e# 3) By identity_hash field (if user entered identity hash but we missed recall, or other announce types)
Te6edf3id_key Tff7b72= Te6edf3canonical Tff7b72or Te6edf3th
Te6edf3announces Tff7b72= Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_filtered_announcesTb4b4b4(
Te6edf3identity_hashTff7b72=Te6edf3id_keyTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3announcesTb4b4b4:
Te6edf3announce Tff7b72= Te6edf3announcesTb4b4b4[T79c0ff0Tb4b4b4]

Tff7b72if Tff7b72not Te6edf3announceTb4b4b4:
Tff7b72return Tff7b72None

T8b949e# Try identity_hash from announce
Te6edf3identity_hex Tff7b72= Te6edf3announceTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffidentity_hashTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3identity_hexTb4b4b4:
Te6edf3id_bytes Tff7b72= Te6edf3hex_identifier_to_bytesTb4b4b4(Te6edf3identity_hexTb4b4b4)
Tff7b72if Te6edf3id_bytesTb4b4b4:
Te6edf3ident Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8ffrecallTb4b4b4(Te6edf3id_bytesTb4b4b4)
Tff7b72if Te6edf3identTb4b4b4:
Tff7b72return Te6edf3ident

T8b949e# Try reconstructing from public key
Tff7b72if Te6edf3announceTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffidentity_public_keyTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72with Te6edf3contextlibTff7b72.Td2a8ffsuppressTb4b4b4(Tf85149ExceptionTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8fffrom_bytesTb4b4b4(
Te6edf3base64Tff7b72.Td2a8ffb64decodeTb4b4b4(Te6edf3announceTb4b4b4[Ta5d6ff"Ta5d6ffidentity_public_keyTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72return Tff7b72None

T8b949e# Find destination identity
Te6edf3destination_identity Tff7b72= Te6edf3resolve_identityTb4b4b4(Te6edf3destination_hash_hexTb4b4b4)

Tff7b72if Te6edf3destination_identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffDiscovering path/identity...Ta5d6ff"Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffrequest_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)

T8b949e# Wait for identity to appear
Te6edf3start_wait Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72while Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3start_wait Tff7b72< Te6edf3timeout_secondsTb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4: T8b949e# Externally cancelled (hangup)
Tff7b72return Tff7b72None
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)
Te6edf3destination_identity Tff7b72= Te6edf3resolve_identityTb4b4b4(Te6edf3destination_hash_hexTb4b4b4)
Tff7b72if Te6edf3destination_identityTb4b4b4:
Tff7b72break

Tff7b72if Te6edf3destination_identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Tff7b72NoneTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffDestination identity not foundTa5d6ff"
Tff7b72raise Tf85149RuntimeErrorTb4b4b4(Te6edf3msgTb4b4b4)

Tff7b72if Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffRequesting path...Ta5d6ff"Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffrequest_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)

T8b949e# Wait up to 10s for path discovery
Te6edf3path_wait_start Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72while Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3path_wait_start Tff7b72< Tffa657minTb4b4b4(Te6edf3timeout_secondsTb4b4b4, T79c0ff10Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4: T8b949e# Externally cancelled
Tff7b72return Tff7b72None
Tff7b72if Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Tff7b72break
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffEstablishing link...Ta5d6ff"Tb4b4b4, Te6edf3destination_hash_hexTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcall_start_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcall_is_incoming Tff7b72= Tff7b72False

T8b949e# Use a thread for the blocking LXST call, but monitor status for early exit
T8b949e# if established elsewhere or timed out/hung up
Te6edf3call_task Tff7b72= Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(
Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcallTb4b4b4, Te6edf3destination_identityTb4b4b4)Tb4b4b4,
Tb4b4b4)

Te6edf3start_wait Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
T8b949e# LXST telephone.call usually returns on establishment or timeout.
T8b949e# We wait for it, but if status becomes established or ended, we can stop waiting.
Tff7b72while Tff7b72not Te6edf3call_taskTff7b72.Td2a8ffdoneTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4: T8b949e# Externally cancelled
Tff7b72break

T8b949e# Update UI status based on current call state
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff2Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffCalling...Ta5d6ff"Tb4b4b4, Te6edf3destination_hash_hexTb4b4b4)
Tff7b72elif Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff4Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6ff"Ta5d6ffRinging...Ta5d6ff"Tb4b4b4, Te6edf3destination_hash_hexTb4b4b4)
Tff7b72elif Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff5Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(
Ta5d6ff"Ta5d6ffEstablishing link...Ta5d6ff"Tb4b4b4,
Te6edf3destination_hash_hexTb4b4b4,
Tb4b4b4)

Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72in Tb4b4b4[
T79c0ff6Tb4b4b4,
T79c0ff0Tb4b4b4,
T79c0ff1Tb4b4b4,
Tb4b4b4]Tb4b4b4: T8b949e# Established, Busy, Rejected
Tff7b72break
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff3 Tff7b72and Tb4b4b4(
Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3start_wait Tff7b72> T79c0ff1.0
Tb4b4b4)Tb4b4b4: T8b949e# Available (ended/timeout)
Tff7b72break
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)

T8b949e# If the task finished but we're still ringing or connecting,
T8b949e# wait a bit more for establishment or definitive failure
Tff7b72if Tff7b72selfTff7b72.Td2a8ffinitiation_status Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72in Tb4b4b4[
T79c0ff2Tb4b4b4,
T79c0ff4Tb4b4b4,
T79c0ff5Tb4b4b4,
Tb4b4b4]Tb4b4b4: T8b949e# Calling, Ringing, Connecting
Te6edf3wait_until Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3timeout_seconds
Tff7b72while Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72< Te6edf3wait_untilTb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffinitiation_statusTb4b4b4: T8b949e# Externally cancelled
Tff7b72break

Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff2Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(
Ta5d6ff"Ta5d6ffCalling...Ta5d6ff"Tb4b4b4,
Te6edf3destination_hash_hexTb4b4b4,
Tb4b4b4)
Tff7b72elif Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff4Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(
Ta5d6ff"Ta5d6ffRinging...Ta5d6ff"Tb4b4b4,
Te6edf3destination_hash_hexTb4b4b4,
Tb4b4b4)
Tff7b72elif Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff5Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(
Ta5d6ff"Ta5d6ffEstablishing link...Ta5d6ff"Tb4b4b4,
Te6edf3destination_hash_hexTb4b4b4,
Tb4b4b4)

Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72in Tb4b4b4[
T79c0ff6Tb4b4b4,
T79c0ff0Tb4b4b4,
T79c0ff1Tb4b4b4,
T79c0ff3Tb4b4b4,
Tb4b4b4]Tb4b4b4: T8b949e# Established, Busy, Rejected, Ended
Tff7b72break
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)

Tff7b72return Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffactive_call

Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"Tb4b4b4)
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff3Tb4b4b4)
Tff7b72raise
Tff7b72finallyTb4b4b4:
T8b949e# Wait for either establishment, failure, or a timeout
T8b949e# to ensure the UI has something to show (either active_call or initiation_status)
Tff7b72for Te6edf3_ Tff7b72in Tffa657rangeTb4b4b4(T79c0ff20Tb4b4b4)Tb4b4b4: T8b949e# Max 10 seconds of defensive waiting
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72and Tb4b4b4(
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffactive_call
Tff7b72or Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72in Tb4b4b4[T79c0ff0Tb4b4b4, T79c0ff1Tb4b4b4, T79c0ff3Tb4b4b4, T79c0ff6Tb4b4b4]
Tb4b4b4)Tb4b4b4:
Tff7b72break
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)

T8b949e# If call was successful, keep status for a moment to prevent UI flicker
T8b949e# while the frontend picks up the new active_call state
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72and Tb4b4b4(
Tb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffactive_call Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72== T79c0ff6Tb4b4b4)
Tff7b72or Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffcall_status Tff7b72in Tb4b4b4[T79c0ff2Tb4b4b4, T79c0ff4Tb4b4b4, T79c0ff5Tb4b4b4]
Tb4b4b4)Tb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff2.0Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_update_initiation_statusTb4b4b4(Tff7b72NoneTb4b4b4, Tff7b72NoneTb4b4b4)

Tff7b72def Td2a8ffmute_transmitTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
T8b949e# Manual override as LXST internal muting can be buggy
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4, Ta5d6ff"Ta5d6ffaudio_inputTa5d6ff"Tb4b4b4) Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_inputTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_inputTff7b72.Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to stop audio input for mute: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)

T8b949e# Still call the internal method just in case it does something useful
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffmute_transmitTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4: T8b949e# noqa: S110
Tff7b72pass

Tff7b72selfTff7b72.Td2a8fftransmit_muted Tff7b72= Tff7b72True

Tff7b72def Td2a8ffunmute_transmitTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
T8b949e# Manual override as LXST internal muting can be buggy
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4, Ta5d6ff"Ta5d6ffaudio_inputTa5d6ff"Tb4b4b4) Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_inputTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_inputTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffFailed to start audio input for unmute: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4,
Tb4b4b4)

T8b949e# Still call the internal method just in case
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffunmute_transmitTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4: T8b949e# noqa: S110
Tff7b72pass

Tff7b72selfTff7b72.Td2a8fftransmit_muted Tff7b72= Tff7b72False

Tff7b72def Td2a8ffmute_receiveTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
T8b949e# Manual override as LXST internal muting can be buggy
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4, Ta5d6ff"Ta5d6ffaudio_outputTa5d6ff"Tb4b4b4) Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_outputTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_outputTff7b72.Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to stop audio output for mute: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)

T8b949e# Still call the internal method just in case
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffmute_receiveTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4: T8b949e# noqa: S110
Tff7b72pass

Tff7b72selfTff7b72.Td2a8ffreceive_muted Tff7b72= Tff7b72True

Tff7b72def Td2a8ffunmute_receiveTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4:
T8b949e# Manual override as LXST internal muting can be buggy
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephoneTb4b4b4, Ta5d6ff"Ta5d6ffaudio_outputTa5d6ff"Tb4b4b4) Tff7b72and Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_outputTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffaudio_outputTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffFailed to start audio output for unmute: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4,
Tb4b4b4)

T8b949e# Still call the internal method just in case
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephoneTff7b72.Td2a8ffunmute_receiveTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4: T8b949e# noqa: S110
Tff7b72pass

Tff7b72selfTff7b72.Td2a8ffreceive_muted Tff7b72= Tff7b72False


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────